12.5.2 ゴルーチンとforループ
Go 1.22よりも前のバージョンとそれ以降のバージョンではforループの動作が異なる。そのため、ゴルーチンで意図した挙動にならない場合があった
Go 1.22よりも前のバージョンは各反復で同じインデックス変数と値変数が使い回される。そのため、下記のコードではvに割り当てられた最後の値が10で、ゴルーチンが実行されるとその値が5回使用される
code:go
package main
import "fmt"
func main() {
a := []int{2, 4, 6, 8, 10}
ch := make(chan int, len(a))
for _, v := range a {
go func() {
ch <- v * 2
}()
}
for i := 0; i < len(a); i++ {
fmt.Println(<-ch) // 全て20で出力される
}
}
Go 1.22以降では各反復で新しいインデックス変数と値変数が作成されるので、各値の倍数が出力される
どうしてもGo 1.22にアップグレードできない場合の解決方法は二つ
ループ内でシャドーイングして値のコピーを作成する
code:go
for _, v := range a {
v := v // シャドーイング
go func() {
ch <- v * 2
}()
}
ゴルーチンの引数に値を渡す
code:go
for _, v := range a {
go func(val int) {
ch <- v * 2
}(v)
}
forループ以外でも、値が変更される可能性のある変数をクロージャが使う場合は、引数を使って変数の現在の値のコピーして渡す方法が推奨される